4 // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
6 // The use and distribution terms for this software are contained in the file
7 // named license.txt, which can be found in the root of this distribution.
8 // By using this software in any fashion, you are agreeing to be bound by the
9 // terms of this license.
11 // You must not remove this notice, or any other, from this software.
15 // Exec.C - Contains routines that have do to with execing programs
18 // Contains routines that spawn programs ...
29 extern BOOL
processInline(char *, char **, STRINGLIST
**, BOOL
);
32 extern UCHAR fRunningUnderChicago
;
34 #define fRunningUnderChicago FALSE
37 char * getComSpec(void);
38 BOOL
iterateCommand(char*, STRINGLIST
*, UCHAR
, UCHAR
, char *, unsigned*);
39 void removeQuotes(int, char **);
40 void touch(char*, BOOL
);
41 void buildArgumentVector(unsigned int *, char **, char *);
44 //buffer for path of .cmd/.bat
45 extern char * makeStr
;
46 extern char * shellName
;
49 char szCmdLineBuf
[MAXCMDLINELENGTH
];
50 char *szNmakeProgName
;
52 // buildArgumentVector -- builds an argument vector from a command line
58 // It builds an argument vector for a command line. This argument vector can
59 // be used by spawnvX routines. The algorithm is explained in the notes below.
62 // argc -- The number of arguments created in the argument vector
63 // argv -- The actual argument vector created
65 // cmdline -- The command line whose vector is required
68 // Returns the number of arguments and the argument vector as parameters
72 // That the behaviour of cmd.exe i.e. parses quotes but does not disturb them.
73 // Assumes that the SpawnVX routines will handle quotes as well as escaped
79 // Scan the cmdline from left to the end building the argument vector along
80 // the way. Whitespace delimits arguments except for the first argument for
81 // which the switch char '/' is also allowed. Backslash can be used to escape
82 // a char and so ignore the character following it. Parse the quotes along
83 // the way. If an argument begins with a double-quote then all characters till
84 // an unescaped double-quote are part of that argument. Likewise, if an
85 // unescaped Doublequote occurs within an argument then the above follows. If
86 // the end of the command line comes before the closing quote then the
87 // argument goes as far as that.
96 char *p
; // current loc in cmdline
97 char *end
; // end of command line
99 // This determines if a '/' is accepted as a terminator for
100 // the first argument, as in the "dir/s" shorthand for "dir /s".
101 // That's never true on Unix.
102 BOOL fFirstTime
= FALSE
;
103 #else // PLATFORM_UNIX
104 BOOL fFirstTime
= TRUE
; // true if 1st argument
105 #endif // PLATFORM_UNIX
111 for (*argc
= 0; p
< end
; ++*argc
) {
112 p
+= _tcsspn(p
, " \t"); // skip whitespace
119 // If the word begins with double-quote, find the next
120 // occurrence of double-quote which is not preceded by backslash
121 // (same escape as C runtime), or end of string, whichever is
122 // first. From there, find the next whitespace character.
124 for (++p
; p
< end
; p
= _tcsinc(p
)) {
126 ++p
; // skip escaped character
133 p
= _tcspbrk(p
, " \t");
136 // For the first word on the command line, accept the switch
137 // character and whitespace as terminators. Otherwise, just
140 p
= _tcspbrk(p
, " \t\"/");
141 for (;p
&& p
< end
;p
= _tcspbrk(p
+1, " \t\"/")) {
142 if (*p
== '/' && !fFirstTime
)
143 continue; // after 1st word '/' is !terminator
146 if (p
&& *p
== '\"') {
147 for (p
++;p
< end
;p
++) { // inside quote so skip to next one
151 p
= _tcspbrk(p
, " \t"); // after quote go to first whitespace
156 // If switch char terminates the word, replace it with 0,
157 // re-allocate the word on the heap, restore the switch and set
158 // p just before the switch. It would be easier to shift
159 // everything right but then we have to worry about overflow.
161 if (p
&& *p
== '/' && argv
) {
163 argv
[-1] = makeString(argv
[-1]);
170 // Now, p points to end of command line argument
189 temp
= makeNewStrListElement();
190 temp
->text
= makeString(name
);
191 rc
= doCommandsEx (temp
, s
, t
, buildFlags
, pFirstDep
);
192 free_stringlist(temp
);
198 STRINGLIST
*nameList
,
217 printf("* doCommands:");
225 printf("DEBUG: doCommands 1\n");
228 if (ON(gFlags
, F1_QUESTION_STATUS
))
231 if (ON(gFlags
, F1_TOUCH_TARGETS
)) {
233 for (pName
= nameList
; pName
; pName
= pName
->next
) {
234 touch(pName
->text
, (USHORT
) ON(buildFlags
, F2_NO_EXECUTE
));
240 printf("DEBUG: doCommands 2\n");
243 for (; s
; s
= s
->next
) {
244 fExpanded
= processInline(s
->text
, &Cmd
, &t
,
245 ON(buildFlags
, F2_DUMP_INLINE
));
249 for (v
= u
; *v
; v
= _tcsinc(v
)) {
251 else if (*v
== '$') {
257 printf("DEBUG: doCommands 2.1\n");
259 for (c
= *u
; c
== '!' ||
263 WHITESPACE(c
); u
= _tcsinc(u
), c
= *u
) {
266 if (c
= *++u
, WHITESPACE(c
))
267 c
= ' '; // keep going
273 SET(cFlags
, C_ITERATE
);
277 SET(cFlags
, C_IGNORE
);
282 errorLevel
= _tcstoul(u
, &u
, 10);
283 if (errno
== ERANGE
) {
285 makeError(line
, CONST_TOO_BIG
, pNumber
);
290 errorLevel
= UINT_MAX
;
295 OFF(flags
, F2_NO_EXECUTE
)) {
296 SET(cFlags
, C_SILENT
);
301 break; // stop parsing for cmd-line options
304 printf("DEBUG: doCommands 2.2\n");
306 if (ON(cFlags
, C_ITERATE
) &&
307 iterateCommand(u
, t
, buildFlags
, cFlags
, pFirstDep
, &status
)
309 // The macros used by the command have to be freed & so we do so
314 printf("DEBUG: doCommands 2.21\n");
317 u
= expandMacros(u
, &t
);
320 printf("DEBUG: doCommands 2.22\n");
324 if (OFF(buildFlags
, F2_IGNORE_EXIT_CODES
) &&
336 printf("DEBUG: doCommands 2.23\n");
338 if (!fExpanded
&& _tcschr(u
, '$'))
339 u
= expandMacros(u
, &t
);
342 printf("DEBUG: doCommands 2.24\n");
345 cbLine
= _tcslen(u
) + 1;
346 pLine
= (char *) rallocate (__max(cbLine
, MAXCMDLINELENGTH
));
349 // by this time $< has already been expanded.
350 // in order to allow processing of long commands that are due to
351 // batch-mode rules, use a buffer that may be larger than MAXCMDLINELENGTH
352 // Later we'll attempt to execute the long command directly, instead of
353 // passing it to the shell.
354 // Note: the macros expanded by ZFormat are not normally found in the
355 // command block of a batch-mode rule, so it should be safe to use
356 // max(cbLine, MAXCMDLINELENGTH) as a limit for ZFormat
357 if (ZFormat (pLine
, __max(cbLine
, MAXCMDLINELENGTH
), u
, pFirstDep
))
358 makeError(0, COMMAND_TOO_LONG
, u
);
360 status
= execLine(pLine
,
361 (BOOL
)(ON(buildFlags
, F2_NO_EXECUTE
)
362 || (OFF(buildFlags
,F2_NO_ECHO
)
363 && OFF(cFlags
,C_SILENT
))),
364 (BOOL
)((OFF(buildFlags
, F2_NO_EXECUTE
)
366 || ON(cFlags
, C_EXECUTE
)),
367 (BOOL
)ON(cFlags
, C_IGNORE
), &pCmd
);
368 if (OFF(buildFlags
, F2_IGNORE_EXIT_CODES
)) {
369 if (status
&& status
> errorLevel
) {
371 makeError(0, BAD_RETURN_CODE
, pCmd
, status
);
378 if (OFF(buildFlags
, F2_IGNORE_EXIT_CODES
) &&
388 printf("DEBUG: doCommands 3\n");
391 if (OFF(buildFlags
, F2_IGNORE_EXIT_CODES
) && fOptionK
&&
392 (status
> errorLevel
))
399 // expandCommandLine -- expands %name% strings in the Command Line
402 // The function expands '%name%' type strings in the Command Line. Its main
403 // job is to assist FEmulateCommand() in emulating set for OS/2.
405 // Modifies: buf -- The Command Line available globally
408 // Returns -- the position of 'name=value' part in the Command Line.
409 // -- Null when no '=' is found so that FEmulateCommand() can pass the
410 // line to the shell to signal syntax error.
412 // The shell does not give a syntax error for unmatched '%' and assumes it
413 // as just another character in this case. This behaviour is duplicated
414 // by expandCommandLine()
421 char Buf
[MAXCMDLINELENGTH
]; // Buffer for expanded string
423 char EnvBuf
[MAXCMDLINELENGTH
]; // getenv returned string copy
424 char *posName
, // position of 'name=string' in Buf or buf
425 *p
, // points into buf
426 *pEnv
; // points into Env
427 char ExpandName
[MAXNAME
]; // %name% string
431 _tcscpy(pBuf
, "set");
432 p
= szCmdLineBuf
+ 3; // go beyond 'set'
434 /* Skip whitespace */
436 if (!(WHITESPACE(*p
)))
437 break; // argc>1 ð this will happen
441 if (!_tcschr(p
, '='))
442 return(""); // Syntax error so pass to the shell
444 posName
= pBuf
; // fixes position of Name in Buf
446 // Now we look for environment variables and expand if required
452 pExpandName
= &ExpandName
[0];
453 while (*++p
!= '%' && *p
)
456 if (!*p
++) { // unmatched %;so don't expand
457 *pBuf
='\0'; // from the environment; like set
458 _tcscat(Buf
, ExpandName
);
459 pBuf
+= _tcslen(ExpandName
);
460 break; // Done precessing quit
461 } else { // matched %;so expand from the environment
463 if ((pEnv
= getenv(ExpandName
)) != (char *)NULL
) {
466 // If the expanded command line is too long
467 // just say that we can't expand it!!! #43290
468 size_t len
= _tcslen(pEnv
) + _tcslen(Buf
);
469 if (len
> MAXCMDLINELENGTH
)
472 _tcscat(EnvBuf
, pEnv
);
474 pBuf
+= _tcslen(EnvBuf
);
481 _tcscpy(szCmdLineBuf
, Buf
);
483 posName
= szCmdLineBuf
+ _tcslen(Buf
); // Offset into buf
487 // expandEnvVars -- expands %name% strings in szArg
489 // Returns -- szNew: the resulting expanded string
490 // (szNew should be FREEd by the caller)
497 char *pchLeft
= NULL
;
498 char *pchRight
= NULL
;
499 char *pchStart
= szArg
;
501 char *szNew
= makeString("");
504 pchLeft
= _tcschr(pchStart
, '%');
506 pchRight
= _tcschr(pchLeft
+ 1, '%');
509 if (pchLeft
&& pchRight
) {
513 szNew
= reallocString(szNew
, pchStart
);
514 if ((szEnv
= getenv(pchLeft
+ 1))) {
515 szNew
= reallocString(szNew
, szEnv
);
518 // no matching env var was found
519 // append the %..% string literary
521 szNew
= reallocString(szNew
, pchLeft
);
522 szNew
= reallocString(szNew
, "%");
526 pchStart
= pchRight
+ 1;
531 szNew
= reallocString(szNew
, pchStart
);
532 pchStart
+= _tcslen(pchStart
);
539 // FEmulateCommand - look for certain commands and emulate them
541 // Emulate $(MAKE), cd, chdir, and <drive letter>:.
542 // Also emulates 'set'.
544 // RETURNS: TRUE if command emulated, FALSE if not.
547 // In set emulation if a syntax error is discovered then it lets the
548 // shell handle it. It does this by returning FALSE.
557 char *pArg0
= argv
[0];
558 char *pArg1
= argv
[1];
560 #ifndef PLATFORM_UNIX
561 if (_istalpha(*pArg0
) && pArg0
[1] == ':' && !pArg0
[2]) {
562 // If "<drive letter>:" then change drives. Ignore everything after
563 // the drive letter, just like the shell does.
565 drive
[0] = _totupper(*pArg0
);
568 SetCurrentDirectory(drive
);
572 #endif //!PLATFORM_UNIX
574 if (!_tcsicmp(pArg0
, "set")) {
575 char *pNameVal
; // the "name=value" string
577 // If "set" then pass it to the shell and if "set string" then put it
578 // into the environment. Let the shell handle the syntax errors.
581 return(FALSE
); // pass it to the shell
584 // expandCommandLine cannot handle lines > MAXCMDLINELENGTH
585 // In that case szCmdLineBuf will be empty
586 if (!szCmdLineBuf
[0])
589 pNameVal
= expandCommandLine();
591 if (pNameVal
== NULL
)
593 // Expanded commad line too long
598 // If there is a syntax error let the shell handle it
603 if ((*pStatus
= PutEnv(makeString(pNameVal
))) == -1) {
604 makeError(currentLine
, OUT_OF_ENV_SPACE
);
607 // If "cd foo" or "chdir foo", do a chdir() else in protect mode this
608 // would be a no-op. Ignore everything after 1st arg, just like the
613 if (!_tcsnicmp(pArg0
, "cd", 2)) {
615 } else if (!_tcsnicmp(pArg0
, "chdir", 5)) {
621 // At this point, a prefix of argv[0] matches cd or chdir and pArg0
622 // points to the next char. Check for a path separator in argv[0]
623 // (e.g., cd..\foo) or else use the next arg if present.
625 // if there are more than two arguments then let the shell handle it
630 // Remove quotes, if any from the argument
631 removeQuotes(argc
, argv
);
633 if (!*pArg0
&& pArg1
) {
634 // Under certain circumstances the C RunTime does not help us
635 // e.g. 'd:', in this case let the shell do it ...
636 if (isalpha(*pArg1
) && pArg1
[1] == ':' && !pArg1
[2]) {
640 szArg
= expandEnvVars(pArg1
);
641 *pStatus
= _chdir(szArg
);
643 } else if (*pArg0
== '.' || IsPathSeparator(*pArg0
)) {
644 szArg
= expandEnvVars(pArg0
);
645 *pStatus
= _chdir(szArg
);
648 // Unrecognized syntax--we can't emulate.
654 // If error, simulate a return code of 1.
665 // execLine -- execute a command line
667 // Scope: Global (build.c, rpn.c)
670 // Parses the command line for redirection characters and redirects stdin and
671 // stdout if "<", ">", or ">>" are seen. If any of the following occur,
672 // restore the original stdin and stdout, pass the command to the shell, and
674 // - the command line contains "|" (pipe)
675 // - a syntax error occurs in parsing the command line
676 // - an error occurs in redirection
677 // Otherwise, attempt to invoke the command directly, then restore the
678 // original stdin and stdout. If this invocation failed because of
679 // file-not-found then pass the command to the shell and invoke the shell.
681 // Input: line -- The command line to be executed
682 // echoCmd -- determines if the command line is to be echoed
683 // doCmd -- determines if the command is to be actually executed
684 // ignoreReturn -- determines if NMAKE is to ignore the return code on
686 // ppCmd -- if non-null then on error returns command executed
688 // Output: Returns ... return code from child process
689 // ... -1 if error occurs
692 // 1/ Quoted strings can have redir chars "<>" which will be skipped over.
693 // 2/ Unmatched quotes cause error; redir chars are replaced by space char.
694 // 3/ Dup stdin file handle then redirect it. If we have to use the shell,
695 // restore the original command line.
696 // 4/ Emulate certain commands such as "cd" to help prevent some makefiles
697 // from breaking when ported from DOS to OS/2.
699 // Algorithm for spawning commands:
700 // If we can't handle the syntax, let the shell do everything. Otherwise,
701 // first check to see if the command (without extension) is a DOS built-in &
702 // if it is, call the shell to execute it (this is how cmd.exe behaves)
703 // If it's not a built-in, we check to see if it has a .cmd or a .bat
704 // extension (depending on whether we're in DOS or OS/2). If it does, we
705 // call system() to execute it.
706 // If it has some other extension, we ignore the extension and go looking for
707 // a .cmd or .bat file. If we find it, we execute it with system().
708 // Otherwise, we try to spawn it (without extension). If the spawn fails,
709 // we issue an unknown program error.
728 shellName
= getComSpec();
733 // Turn off echo if it was on. This handles the case where the "@"
744 if (_istdigit(*line
)) {
745 char * pNumber
= line
;
746 errorLevel
= _tcstoul(line
, &line
, 10);
747 if (errno
== ERANGE
) {
749 makeError(0, CONST_TOO_BIG
, pNumber
);
751 while(_istspace(*line
))
754 errorLevel
= UINT_MAX
;
758 // handle null command ...
763 fLongCommand
= _tcslen(line
) >= MAXSHELLCMDLINELENGTH
;
765 _tcscpy(szCmdLineBuf
, line
);
767 *szCmdLineBuf
= '\0';
769 // Allocate a copy of the command line on the heap because in a
770 // recursive call to doMake(), argv pointers will be allocated from
771 // the static buffer which will then be trashed. For buildArg...().
773 pCmdLineCopy
= makeString(line
);
776 // Replace any path separators with the native path separator.
777 char *tmp
= pCmdLineCopy
;
778 while((tmp
= FindFirstPathSeparator(tmp
))) {
779 *tmp
++ = PATH_SEPARATOR_CHAR
;
781 #endif // PLATFORM_UNIX
783 // If -n then echo command if not '$(MAKE)'
785 printf("\t%s\n", pCmdLineCopy
);
789 // Build arg vector. This is a waste on Windows NT since we're probably
790 // going to use the shell, except we have to check for cd, $(MAKE),
791 // etc. so we take advantage of the parsing code.
793 buildArgumentVector(&argc
, NULL
, pCmdLineCopy
);
796 return(0); // for case when macro command is null
799 // allocate argv. Leave space for extra arguments
800 // (like "cmd", "/k", quotes) that may be added later
801 argv
= (char **) rallocate((argc
+ 5) * sizeof (char *));
802 buildArgumentVector(&argc
, argv
, pCmdLineCopy
);
804 // The _mbsicmp() does not like NULL pointer
805 // so I have to check before calling it.
806 if (argv
[0] && makeStr
&& !_tcsicmp(argv
[0], makeStr
)) {
807 if(!szNmakeProgName
) {
808 szNmakeProgName
= _pgmptr
;
809 if( _tcspbrk( szNmakeProgName
," " )) { // If the program name has an embedded space in it
810 // Let's put quotes around it
811 szNmakeProgName
= (char *)rallocate(_tcslen(szNmakeProgName
)+3);
812 *szNmakeProgName
= QUOTE
; // First quote
813 *(szNmakeProgName
+1) = '\0';
814 _tcscat( szNmakeProgName
, _pgmptr
); // copy the full program name (self)
815 _tcscat( szNmakeProgName
, "\""); // Final quote and \0
818 argv
[0]=szNmakeProgName
;
821 if (!doCmd
) { // don't execute command if doCmd false
822 // For -n, emulate if possible.
824 if (FEmulateCommand(argc
, argv
, &status
)) {
825 if (status
&& ppCmd
) {
826 *ppCmd
= makeString(*argv
);
829 return(status
); // return status
835 // Try emulating the command if appropriate. If not, and we should not
836 // use the shell, try spawning command directly.
838 // Check status when emulating
840 if (FEmulateCommand(argc
, argv
, &status
)) {
841 // Command has been emulated. Don't execute it again.
852 BOOL fExtraQuote
= TRUE
;
854 BOOL fExtraQuote
= FALSE
;
856 // copy command line into buffer
857 if (_tcslen(line
) < MAXCMDLINELENGTH
)
858 _tcscpy(szCmdLineBuf
, line
);
860 makeError(0, COMMAND_TOO_LONG
, line
);
862 // IF argv[0] (before we rearrange with cmd.exe /c) is quoted AND
863 // any of the other argv[1...n] args have quotes AND
865 // THEN we add an extra quote before argv[0] and one after argv[n].
867 if ((*argv
[0] == QUOTE
) &&
868 (*(argv
[0] + _tcslen(argv
[0]) - 1) == QUOTE
) &&
869 !fRunningUnderChicago
) {
870 for (i
= argc
- 1; i
>= 1; i
--) {
871 if( _tcspbrk( argv
[i
],"\"" )) {
884 for (i
= argc
; i
>= 0; i
--) {
889 argv
[1] = fExtraQuote
? (char *)"-c \"" : (char *)"-c";
891 argv
[1] = fExtraQuote
? (char *)"/c \"" : (char *)"/c";
896 status
= _spawnvp(P_WAIT
, argv
[0], (const char * const *)&argv
[1]);
898 status
= _spawnvp(P_WAIT
, argv
[0], (const char * const *)argv
);
903 // Check for errors spawning command (distinct from errors *returned*
904 // from a successfully spawned command).
912 // We (ie: nmake) didn't fail, but the spawned program did.
916 makeError(0, CANT_FIND_PROGRAM
, argv
[0]);
920 makeError(0, EXEC_NO_MEM
, fUseShell
? argv
[2] : argv
[0]);
924 // Done to flag possibly erroneous decision made here [SB]
925 makeError(0, SPAWN_FAILED_ERROR
, _strerror(NULL
));
930 if (status
&& ppCmd
) {
931 *ppCmd
= makeString(fUseShell
? argv
[2] : argv
[0]);
942 // actions: Attempts to find system shell.
944 // First look for COMSPEC. If not found, look for COMMAND.COM or CMD.EXE
945 // in the current directory then the path. If not found, fatal error.
946 // It would make sense to give an error if COMSPEC is not defined but
947 // test suites are easier if no user-defined environment variables are
954 char szPath
[_MAX_PATH
];
956 if ((szShell
= getenv("COMSPEC")) != NULL
) {
961 strcpy(szPath
, "/bin/sh");
962 #else // PLATFORM_UNIX
963 szShell
= (char *)"system32\\cmd.exe";
964 if (!GetEnvironmentVariable("systemroot",
966 sizeof(szPath
)-strlen(szShell
))) {
967 makeError(0, NO_COMMAND_COM
);
969 strcat(szPath
, szShell
);
970 #endif // PLATFORM_UNIX
972 return(makeString(szPath
));
989 STRINGLIST
*p
= NULL
,
994 for (v
= u
; *v
; ++v
) {
1001 if (*(v
+1) == '?') {
1003 && !(_tcschr("DFBR", *(v
+2)) && *(v
+3) == ')')
1010 if (*++v
== '*' && *(v
+1) == '*') {
1012 && !(_tcschr("DFBR", *(v
+2)) && *(v
+3) == ')')
1031 p
= dollarStarStar
->next
;
1032 dollarStarStar
->next
= NULL
;
1034 p
= dollarQuestion
->next
;
1035 dollarQuestion
->next
= NULL
;
1037 u
= expandMacros(v
, ¯os
);
1039 expandExtmake(CmdLine
, u
, pFirstDep
);
1041 *status
= execLine(pLine
,
1042 (BOOL
)(ON(buildFlags
, F2_NO_EXECUTE
)
1043 || (OFF(buildFlags
,F2_NO_ECHO
)
1044 && OFF(cFlags
,C_SILENT
))),
1045 (BOOL
)((OFF(buildFlags
, F2_NO_EXECUTE
)
1047 || ON(cFlags
, C_EXECUTE
)),
1048 (BOOL
)ON(cFlags
, C_IGNORE
), &pCmd
);
1049 if (OFF(buildFlags
, F2_IGNORE_EXIT_CODES
)) {
1050 if (*status
&& *status
> errorLevel
)
1052 makeError(0, BAD_RETURN_CODE
, pCmd
, *status
);
1056 dollarStarStar
= dollarStarStar
->next
= p
;
1058 dollarQuestion
= dollarQuestion
->next
= p
;
1060 if (OFF(buildFlags
, F2_IGNORE_EXIT_CODES
) &&
1063 *status
> errorLevel
)
1085 for (; argc
--; argv
++) {
1087 for (t
= string
; *t
;) {
1088 if (*t
== SLASH
|| *t
== ESCH
) {
1090 *(string
)++ = *(t
++);
1091 *(string
++) = *(t
++);
1097 if (_istlead(* (unsigned char *)t
))
1098 *(string
++) = *(t
++);
1099 *(string
++) = *(t
++);
1112 // If name contains Quotes, remove these before opening the file
1115 *(_tcsrchr(s
, '"')) = '\0';
1119 makeMessage(TOUCHING_TARGET
, s
);
1127 GetSystemTimeAsFileTime(&ft
);
1129 HANDLE hf
= CreateFile(s
,
1134 FILE_ATTRIBUTE_NORMAL
,
1137 if (hf
== INVALID_HANDLE_VALUE
) {
1141 SetFileTime(hf
, NULL
, NULL
, &ft
);